/*
 *
 *  Copyright (C) 2010 Tal Eisenberg
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package gui;

import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.List;
import javax.swing.JPanel;
import tal.drivechain.chain.AbortChainException;
import tal.drivechain.gui.GUIModule;
import tal.drivechain.matchers.VarMatcher;
import tal.drivechain.parsing.Range;
import tal.drivechain.plugin.Channel;
import tal.drivechain.variable.types.DataType;
import tal.drivechain.variable.SetProperty;
import tal.drivechain.variable.SetProperty.Flag;
import tal.drivechain.variable.Variable;

/**
 *
 * @author Main
 */
public class Plotter extends GUIModule {
    PlotPainter plotPanel;
    Container window;
    VarMatcher trigger;

    @Override
    public void setupProperties() throws IllegalArgumentException, AbortChainException{
        component = plotPanel = new PlotPainter();
        super.setupProperties();
    }

    @Override
    public void setNegative(Boolean value) {
        super.setNegative(value);
        plotPanel.setNegative(negative);
    }

    @Override
    public void setFont(Font value) {
        super.setFont(value);
        plotPanel.setFont(font);
        relayout();
    }

    @Override
    public void setColor(Color value) {
        super.setColor(value);
        plotPanel.setColor(color);
    }

    public void setWidth(Integer value) {
        Dimension d = new Dimension(value, plotPanel.getHeight());
        plotPanel.setPreferredSize(d);
        plotPanel.setSize(d);
        relayout();
    } public int getWidth() { return plotPanel.getWidth(); }

    public void setHeight(Integer value) {
        Dimension d = new Dimension(plotPanel.getWidth(), value);
        plotPanel.setPreferredSize(d);
        plotPanel.setSize(d);
        relayout();
    } public int getHeight() { return plotPanel.getHeight(); }

    public void setLabel(String value) {
        plotPanel.setLabel(value);
    } public String getLabel() { return plotPanel.getLabel(); }

    public void setTrigger(VarMatcher value) {
        trigger = value;
    } public Object getTrigger() { return trigger; }
    
    public void setRange(Range value) {
        plotPanel.setRange(value.getOrderedRange());
    } public Range getRange() { return new Range(plotPanel.getRange()); }
    
    @Override
    protected void msgReceived(Variable in, long timestamp, Channel channel) {
        if (trigger.matches(in, channel, this)) {
            this.setInVars(in, timestamp, channel);
            try {
                plotPanel.addCoordinate((Double)DataType.FLOAT.cast(in.getValue(this)));
            } catch (IllegalArgumentException ie) {
                log("Invalid matching incoming variable: " + in + " (" + ie.getMessage() + ").", MSG.WARNING);
            }
        }
    }

    public void commandClear(List<Variable> parameters) {
        plotPanel.clear();
    }
    
}
class PlotPainter extends JPanel implements MouseMotionListener {
    private List<Double> buffer;
    private int curBufLoc = 0;
    private double min, max;
    private int scaleHeight, scaleWidth;
    private int infoX, infoY, fontHeight;
    private int minX, maxX, middleX;
    private int minY, maxY, middleY;
    private int plotX0;
    private DecimalFormat format = new DecimalFormat("#####.##");
    private boolean firstPaint = true;
    private Color color;
    private boolean negative = false;
    private Color darker;
    private Color xor;
    private String label;
    private int labelX, labelY;
    private Color outofRangeColor;

    PlotPainter() {
        this(new double[] { 0, 1}, Color.ORANGE, null);
    }
    
    PlotPainter(double[] range, Color color, String label) {
        super();
        this.setBackground(Color.BLACK);
        this.min = range[0];
        this.max = range[1];
        this.label = label;
        setColor(color);
        this.addMouseMotionListener(this);
        this.addComponentListener(new ComponentAdapter() {
            @Override
            public void componentResized(ComponentEvent e) {
                firstPaint = true;
                repaint();
            }
            @Override
            public void componentShown(ComponentEvent e) {
                firstPaint = true;
                repaint();
            }
        });

        this.setMinimumSize(new Dimension(50,50));
    }
    
    public void clear() {
        if (buffer!=null) {
            curBufLoc = 0;
            buffer.clear();
            getGraphics().clearRect(0, 0, this.getWidth(), this.getHeight());
            this.repaint();
        }
    }
    
    public void setRange(double[] range) {
        this.clear();
        firstPaint = true;
        min = range[0];
        max = range[1];
        repaint();
    }
    
    @Override
    public void paintComponent(Graphics g) {
        // only first time
        if (firstPaint) {
            g.clearRect(0, 0, getWidth(), getHeight());
            fontHeight = this.getGraphics().getFontMetrics().getHeight();            
            scaleHeight = getHeight() - (fontHeight + 5);
            minX = this.getGraphics().getFontMetrics().stringWidth(format.format(min));
            maxX = this.getGraphics().getFontMetrics().stringWidth(format.format(max));
            middleX = this.getGraphics().getFontMetrics().stringWidth(format.format((min+max)/2));
            if (minX>maxX && minX>middleX) scaleWidth = minX;
            else if (maxX>minX && maxX>middleX) scaleWidth = maxX;
            else scaleWidth = middleX;
            minY = getHeight() - 5;
            maxY = 1 + fontHeight;
            middleY = scaleHeight/2 + fontHeight/2;            
            infoY = minY;
            infoX = scaleWidth + 15;
            if (label!=null) {
                labelX = getWidth() - this.getGraphics().getFontMetrics().stringWidth(label) - 5;
                labelY = infoY;
            }
            plotX0 = scaleWidth + 11;
            if (this.getWidth()<plotX0)
                buffer = new ArrayList<Double>();
            else buffer  = new ArrayList<Double>(this.getWidth() - plotX0);
            firstPaint = false;            
        }

        // print scale numbers
        if (negative) g.setColor(Color.BLACK);
        else g.setColor(color);
        g.drawString(format.format(max), 5 + scaleWidth - maxX, maxY);
        g.drawString(format.format(min), 5 + scaleWidth - minX, minY);
        g.drawString(format.format((max+min)/2), 5 + scaleWidth - middleX, middleY);
        
        // print scales
        if (negative) g.setColor(Color.BLACK);
        else g.setColor(xor);
        g.drawLine(scaleWidth + 3, scaleHeight, getWidth()-5, scaleHeight); //x-axis
        g.drawLine(scaleWidth+10, 5, scaleWidth+10, scaleHeight + 10); //y-axis
        if (label!=null) g.drawString(label, labelX, labelY);
        
        //repaint plot
        if (buffer.size()>0) {
            for (int i=0; i<buffer.size(); i++) {
                try {
                    paintCoordinate(g, i);
                } catch (ConcurrentModificationException ce) {
                    System.out.println("concurrent modification exception: " + ce);
                }
            }
        }
    }
    
    public void addCoordinate(double value) {
        if (buffer==null) return;
        if (curBufLoc>=this.getWidth()-plotX0) {
            curBufLoc = 0;
            clear();
        }
        int y = (int) (scaleHeight - ((value-min) * scaleHeight) / (max - min));
        buffer.add(value);
        Graphics g = this.getGraphics();
        if (g!=null && !buffer.isEmpty()) {
            paintCoordinate(g, buffer.size()-1);
        }
        curBufLoc++;        
    }

    public void setLabel(String label) {
        this.label = label;
        firstPaint = true;
    }

    public String getLabel() {
        return label;
    }

    public double[] getRange() {
        return new double[] { min, max };
    }

    public void setColor(Color color) {
        this.color = color;
        this.darker = color.darker();
        this.xor = new Color(color.getRed()^255, color.getGreen()^255, color.getBlue()^255);
        this.outofRangeColor = new Color(color.getGreen(), color.getBlue(), color.getRed());
        repaint();
    }

    void setNegative(boolean negative) {
        this.negative = negative;
        if (negative) setBackground(color);
        else setBackground(Color.BLACK);
    }

    private int findY(int x) {
        double value = (Double)buffer.get(x);
        return (int) (scaleHeight - ((value-min) * scaleHeight) / (max - min));
    }

    private void paintCoordinate(Graphics g, int x) {
        int cx = x + plotX0;
        g.setColor(Color.WHITE);
        if (x>0 && x<buffer.size()) {
            double value = (Double)buffer.get(x);
            int y = findY(x);
            if (buffer.size()>1) {
                g.drawLine(cx-1, findY(x-1), cx, y);
            }
            else g.drawLine(cx, y, cx, y);

            // print value and current frame:
            g.setColor(Color.BLACK);
            g.fillRect(infoX, infoY - fontHeight+1, getWidth(), fontHeight);
            if (value<=this.max)
                g.setColor(color);
            else g.setColor(outofRangeColor);
            g.drawString("frame " + format.format(x) + ": " + value, infoX, infoY);
        }

        
    }

    @Override
    public void setFont(Font font) {
        super.setFont(font);
        this.clear();
        firstPaint = true;
    }

    public void mouseDragged(MouseEvent e) {
    }

    public void mouseMoved(MouseEvent e) {
    }
}





